/* 
 * Copyright 2010 Ziya Guven Koylu, Ozge Koylu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.koylu.caffein.model.clazz;

import java.sql.ResultSet;
import java.util.Collection;
import java.util.List;

import com.koylu.caffein.converter.Converter;
import com.koylu.caffein.converter.ConverterManager;
import com.koylu.caffein.exception.ClazzConfigException;
import com.koylu.caffein.model.caffein.CaffeinConfig;
import com.koylu.caffein.model.clazz.id.CompositeId;
import com.koylu.caffein.model.clazz.validation.ValidateFor;
import com.koylu.caffein.model.clazz.validation.Validation;

public class Property implements ColumnField {

	private String name;
	private String column;
	private String columnAlias;
	private String type;
	private String converter;
	private Converter converterObject;
	private Node parent;
	private String path;
	private String pathForId;
	private List<Validation> validations;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getColumn() {
		return column;
	}

	public void setColumn(String column) {
		this.column = column;
	}
	
	public String getColumnAlias() {
		return columnAlias;
	}
	
	public void setColumnAlias(String columnAlias) {
		this.columnAlias = columnAlias;
	}
	
	public String getType() {
		return type;
	}
	
	public void setType(String type) {
		this.type = type;
	}
	
	public void setColumnType(String columnTypeName) throws Exception{
		if( !"".equals(columnTypeName) && columnTypeName != null ){
			try {
				columnType = ConverterManager.getJdbcTypeValue(columnTypeName);
				if(columnType == null){
					throw new ClazzConfigException("column-type not found:"+columnTypeName, this);
				}
			} catch (Exception e) {
				throw e;
			}			
		}
	}
	
	public void setColumnNullable(boolean columnNullable) throws Exception{
		this.columnNullable = columnNullable;
	}

	public String getConverter() {
		return converter;
	}

	public void setConverter(String converter) {
		this.converter = converter;
	}
	
	public void setConverterObject(Converter converterObject) {
		this.converterObject = converterObject;
	}

	public Node getParent() {
		return parent;
	}

	public void setParent(Node parent) {
		this.parent = parent;
	}

	public String getPath(CaffeinConfig caffeinConfig) throws Exception {
		if (path == null) {
			path = getName();
			Node clazz = getParent();
			while(clazz.getParent() != null){//!(clazz instanceof Clazz)
				path = ((Field) clazz).getName() + "." + path;
				clazz = clazz.getParent();
			}
		}
		return path;
	}

	public String getPathForId(CaffeinConfig caffeinConfig) throws Exception {
		if (pathForId == null) {
			pathForId = getPath(caffeinConfig);
			if (pathForId.contains(".")) {
				pathForId = pathForId.substring(pathForId.indexOf(".") + 1, pathForId.length());
			} else {
				pathForId = "";
			}
		}
		return pathForId;
	}

	public Collection<Node> getChildren() {
		return null;
	}

	public Converter getConverter(CaffeinConfig caffeinConfig) throws Exception {
		if (converterObject == null) {
			int columnType = getColumnType(caffeinConfig);
			Class<?> fieldType = getRealClass(caffeinConfig);
			if(fieldType.isPrimitive()){
				Class<?> temp = ConverterManager.getPrimitiveMap().get(fieldType);
				if(temp != null){
					fieldType = temp;
				}
			}
			if (converter != null && !"".equals(converter)) {
				try {
					converterObject = (Converter) Class.forName(converter).newInstance();
					converterObject.setJdbcType(columnType);
					converterObject.setFieldType(fieldType);
				} catch (Exception e) {
					throw new ClazzConfigException("invalid converter class:"+converter, e, this);
				}
			} else {
				converterObject = ConverterManager.getConverterObject(columnType, fieldType);
				if(converterObject == null){
					throw new ClazzConfigException("converter not found for columnType:"+ConverterManager.getJdbcTypeName(columnType)+" fieldType:"+fieldType.getCanonicalName(), this);
				}
			}
		}
		return converterObject;
	}

	public Column getRefNode(CaffeinConfig caffeinConfig) {
		return this;
	}

	public ColumnField getRealNode(CaffeinConfig caffeinConfig) {
		return this;
	}

	public Object toObject(CaffeinConfig caffeinConfig, Object object, ResultSet resultSet) throws Exception {
		return getConverter(caffeinConfig).columnToField(resultSet, columnAlias);
	}
	
	public Object toEmptyObject(CaffeinConfig caffeinConfig, Clazz clazz) throws Exception {
		return null;
	}
	
	private Class<?> realClass;
	public Class<?> getRealClass(CaffeinConfig caffeinConfig) throws Exception {
		if(realClass == null){
			if(virtualClass == null){
				Class<?> clazz = null;
				if (getParent().getParent() == null) {//getParent() instanceof Clazz
					clazz = ((Clazz)getParent()).getRealClass(caffeinConfig);
				} else {//CompositeID
					clazz = ((CompositeId)getParent()).getRealClass(caffeinConfig);
				}
				realClass = CaffeinConfig.getFieldType(clazz, name);
			}else{
				realClass = virtualClass;
			}			
		}
		return realClass;
	}

	private Class<?> virtualClass;
	public Class<?> toVirtualClass(CaffeinConfig caffeinConfig) throws Exception {
		if(virtualClass == null){
			if( type == null || "".equals(type)  ){
				int columnType = getColumnType(caffeinConfig);
				if(ConverterManager.getTypeMapForVirtualClass().containsKey(columnType)){
					virtualClass = ConverterManager.getTypeMapForVirtualClass().get(columnType);
				}else{
					throw new ClazzConfigException("no predefined type found for column type:"+ConverterManager.getJdbcTypeName(columnType), this);
				}
			}else{
				try {
					virtualClass = Class.forName(type);	
				} catch (Exception e) {
					throw new ClazzConfigException("property type not found:"+type, e, this);
				}				
			}
		}
		return virtualClass;
	}

	public Object copy(CaffeinConfig caffeinConfig, Object object) throws Exception {
		return object;
	}

	public List<Validation> getValidations() {
		return validations;
	}

	public void setValidations(List<Validation> validations) {
		this.validations = validations;
	}
	
	public void validate(CaffeinConfig caffeinConfig, Clazz clazz, Object object, ValidateFor validateFor) throws Exception {
		if (validations != null) {
			for (Validation validation : validations) {
				validation.validate(caffeinConfig, clazz, this, object, validateFor);
			}
		}
	}
	
	private Integer columnType = null;
	public int getColumnType(CaffeinConfig caffeinConfig) throws Exception {
		if(columnType == null){
			Node clazz = getParent();
			while(clazz.getParent() != null){//!(clazz instanceof Clazz)
				clazz = clazz.getParent();
			}
			try {
				columnType = caffeinConfig.getColumnType(((Clazz) clazz).getTable(), getColumn());
			} catch (Exception e) {
				throw new ClazzConfigException("column or table not found", e, this);
			}
		}
		return columnType;
	}

	private Boolean columnNullable = null;
	public boolean isColumnNullable(CaffeinConfig caffeinConfig) throws Exception {
		if(columnNullable == null){
			Node clazz = getParent();
			while(clazz.getParent() != null){//!(clazz instanceof Clazz)
				clazz = clazz.getParent();
			}
			try {
				columnNullable = caffeinConfig.isColumnNullable(((Clazz) clazz).getTable(), getColumn());
			} catch (Exception e) {
				throw new ClazzConfigException("column or table not found", e, this);
			}
		}
		return columnNullable;
	}

	public String toString() {
		return getClass().getName()+"["+name+"]";
	}
}